﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Configuration;
using System.Reflection.Emit;

using LeanEngine;
using LeanEngine.Reflection;

namespace LeanEngine.Data
{
    public struct PluginProvider
    {
        #region Declaration Members

        private static Dictionary<Type, Func<object>> _activatorDictionary;

        #endregion

        #region Constructor Members

        static PluginProvider()
        {
            _activatorDictionary = new Dictionary<Type, Func<object>>();
            
            var executingAssembly = Assembly.GetExecutingAssembly();
            var appPath = executingAssembly.Location.Replace(executingAssembly.GetName().Name + ".dll", string.Empty);

            InitializeActivatorDictionary(appPath);
        }

        #endregion

        #region Method Members

        public static T LoadInterface<T>()
        {
            var paramType = typeof(T);
            return (T)_activatorDictionary[paramType]();
        }

        private static void InitializeActivatorDictionary(string appPath)
        {
            var pluginName = DataProvider.DefaultConnection.ProviderName + ".Plugin.dll";
            var pluginAssembly = Assembly.LoadFile(appPath + pluginName);
            var getInterfaces = new Func<Type, Type[]>((type) => type.GetInterfaces());
            var hasInterfaces = new Func<Type, bool>((type) => type.GetInterfaces().Length > 0);
            var hasConstructors = new Func<Type, bool>((type) => type.GetConstructors().Length > 0);

            foreach (var item in pluginAssembly.GetTypes().Where(t => hasInterfaces(t) && hasConstructors(t)))
            {
                var activator = CreateConstructorDelegate(item);
                foreach (var interfaceType in getInterfaces(item))
                {
                    _activatorDictionary.Add(interfaceType, activator);
                }
            }
        }

        private static Func<object> CreateConstructorDelegate(Type classType)
        {
            var typeConstructor = classType.GetConstructor(Type.EmptyTypes);

            var dynamic = new DynamicMethod(string.Empty, classType, new Type[0], typeConstructor.DeclaringType);
            var il = dynamic.GetILGenerator();

            il.DeclareLocal(typeConstructor.DeclaringType);
            il.Emit(OpCodes.Newobj, typeConstructor);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            return dynamic.CreateDelegate(typeof(Func<object>)) as Func<object>;
        }

        #endregion
    }
}
